/*
 * Copyright (C) 2011, EADS France
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */

package xowl.interpreter.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Stack;
import xowl.interpreter.Hook;
import xowl.interpreter.XOWLException;

/**
 *
 * @author Laurent WOUTERS
 */
public class ThreadRunner implements xowl.interpreter.Thread {
    private static final String defaultRuntime = "http://www.xowl.org/interpreter/runtime";
    
    private static int counter = 0;
    
    protected java.lang.Thread javaThread;
    protected BaseRepository repository;
    protected VMDynamicEvaluator dynEvaluator;
    protected AggregatedEvaluator evaluator;
    protected xowl.lang.owl2.Ontology runtimeOntology;
    protected Stack<Stack<StatementRunner>> stackStatements;
    protected Stack<Stack<ThreadContext>> stackContexts;
    protected Stack<xowl.lang.runtime.Closure> stackClosures;
    protected xowl.lang.runtime.Value returnValue;
    protected XOWLException returnException;
    
    @Override public boolean isRunning() { return (javaThread != null); }
    @Override public xowl.interpreter.Repository getRepository() { return repository; }
    @Override public xowl.lang.owl2.Ontology getRuntimeOntology() { return runtimeOntology; }
    @Override public void setRuntimeOntology(xowl.lang.owl2.Ontology onto) { runtimeOntology = onto; }

    @Override public xowl.lang.runtime.Value evaluate(xowl.lang.owl2.Expression expression) throws XOWLException { return evaluator.evaluate(evaluator, expression); }
    
    public ThreadRunner(BaseRepository repo, AggregatedEvaluator evaluator) {
        this.repository = repo;
        this.dynEvaluator = new VMDynamicEvaluator(this);
        this.evaluator = evaluator;
        this.evaluator.addPart(dynEvaluator);
        this.runtimeOntology = repository.resolveOntology(defaultRuntime);
        this.stackStatements = new Stack<Stack<StatementRunner>>();
        this.stackContexts = new Stack<Stack<ThreadContext>>();
        this.stackClosures = new Stack<xowl.lang.runtime.Closure>();
    }
    
    @Override public void addHook(Hook hook) { dynEvaluator.addHook(hook); }
    @Override public void addHooks(Collection<Hook> hooks) { dynEvaluator.addHooks(hooks); }
    @Override public void removeHook(Hook hook) { dynEvaluator.removeHook(hook); }
    @Override public void removeHooks(Collection<Hook> hooks) { dynEvaluator.removeHooks(hooks); }
    
    @Override public xowl.lang.runtime.Value run(xowl.lang.runtime.Value function, xowl.lang.runtime.Value[] params) throws XOWLException {
        return run_do(function, params);
    }
    @Override public void runBegin(final xowl.lang.runtime.Value function, final xowl.lang.runtime.Value[] params) throws XOWLException {
        if (javaThread != null)
            throw new XOWLException("Thread is already running !");
        returnException = null;
        javaThread = new java.lang.Thread(new java.lang.Runnable() {
            @Override public void run() {
                try { returnValue = run_do(function, params); }
                catch (XOWLException ex) { returnException = ex; }
            }
        }, "XOWL VM Thread " + counter);
        counter++;
        javaThread.start();
    }
    @Override public xowl.lang.runtime.Value runEnd() throws XOWLException {
        if (javaThread.getState() != java.lang.Thread.State.TERMINATED) {
            try { javaThread.join(); }
            catch (java.lang.InterruptedException ex) { returnException = new XOWLException(ex); }
        }
        javaThread = null;
        if (returnException != null)
            throw returnException;
        xowl.lang.runtime.Value temp = returnValue;
        returnValue = null;
        return temp;
    }
    private xowl.lang.runtime.Value run_do(xowl.lang.runtime.Value function, xowl.lang.runtime.Value[] params) throws XOWLException {
        xowl.lang.runtime.Closure closure = null;
        if (function instanceof xowl.lang.runtime.Closure)
            closure = (xowl.lang.runtime.Closure)function;
        else if (function instanceof xowl.lang.runtime.Entity) {
            xowl.lang.runtime.Entity entity = (xowl.lang.runtime.Entity)function;
            for (xowl.lang.runtime.Interpretation interpretation : entity.getAllInterpretedAs()) {
                if (interpretation instanceof xowl.lang.runtime.Function) {
                    for (xowl.lang.runtime.Closure c : ((xowl.lang.runtime.Function)interpretation).getAllDefinedAs()) {
                        if (c.getCloses().getAllBoundVars().size() == params.length) {
                            closure = c;
                            break;
                        }
                    }
                    if (closure == null)
                        throw new xowl.interpreter.XOWLException("Cannot find closure with " + params.length + " parameters for function " + entity.getHasIRI().getHasValue());
                    break;
                }
            }
            if (closure == null)
                throw new xowl.interpreter.XOWLException("Entity " + entity.getHasIRI().getHasValue() + " has no function interpretation");
        }
        if (closure == null)
            throw new xowl.interpreter.XOWLException("Value of type " + function.getClass().getCanonicalName() + " cannot be executed");
        xowl.interpreter.impl.ThreadContext head = new xowl.interpreter.impl.ThreadContext();
        xowl.interpreter.impl.ThreadContext context = new xowl.interpreter.impl.ThreadContext(head);
        int i = 0;
        for (xowl.lang.actions.CodeVariable param : closure.getCloses().getAllBoundVars()) {
            context.assign(param, params[i]);
            i++;
        }
        return execute(closure, context);
    }
    
    public xowl.lang.runtime.Value execute(xowl.lang.runtime.Closure closure, ThreadContext context) throws XOWLException {
        execute_StepInto(closure, context);
        while (!stackStatements.peek().isEmpty()) {
            try { stackStatements.peek().peek().execute(); }
            catch (XOWLException ex) {
                fill(ex);
                throw ex;
            } catch (java.lang.Exception ex) {
                XOWLException error = new XOWLException(ex);
                fill(error);
                throw error;
            }
        }
        execute_StepOut();
        xowl.lang.runtime.Value temp = returnValue;
        returnValue = null;
        return temp;
    }
    protected void execute_StepInto(xowl.lang.runtime.Closure closure, ThreadContext content) throws XOWLException {
        stackContexts.push(new Stack<ThreadContext>());
        stackContexts.peek().push(content.getParent());
        stackContexts.peek().push(content);
        stackStatements.push(new Stack<StatementRunner>());
        stackClosures.push(closure);
        returnValue = null;
        if (closure.getCloses().getContains() != null)
            statementPush(closure.getCloses().getContains());
    }
    protected void execute_StepOut() {
        stackClosures.pop();
        stackStatements.pop();
        stackContexts.pop();
    }

    public void assign(xowl.lang.actions.CodeVariable var, xowl.lang.runtime.Value value) {
        stackContexts.peek().peek().assign(var, value);
    }
    public xowl.lang.runtime.Value evaluate(xowl.lang.actions.CodeVariable variable) {
        if (variable == xowl.lang.actions.CodeVariable.get_This())
            return stackClosures.peek();
        else if (variable == xowl.lang.actions.CodeVariable.get_Null())
            return null;
        return stackContexts.peek().peek().get(variable);
    }
    
    public void statementPush(xowl.lang.actions.Statement statement) {
        if (statement == null) return;
        java.lang.Class c = statement.getClass();
        if (c == xowl.lang.actions.Add.class) stackStatements.peek().push(new StatementAddRunner((xowl.lang.actions.Add)statement, this));
        else if (statement instanceof xowl.lang.actions.Remove) stackStatements.peek().push(new StatementRemoveRunner((xowl.lang.actions.Remove) statement, this));
        else if (statement instanceof xowl.lang.actions.Assign) stackStatements.peek().push(new StatementAssignRunner((xowl.lang.actions.Assign) statement, this));
        else if (statement instanceof xowl.lang.actions.Block) stackStatements.peek().push(new StatementBlockRunner((xowl.lang.actions.Block)statement, this));
        else if (statement instanceof xowl.lang.actions.Break) stackStatements.peek().push(new StatementBreakRunner((xowl.lang.actions.Break)statement, this));
        else if (statement instanceof xowl.lang.actions.For) stackStatements.peek().push(new StatementForRunner((xowl.lang.actions.For)statement, this));
        else if (statement instanceof xowl.lang.actions.ForEach) stackStatements.peek().push(new StatementForEachRunner((xowl.lang.actions.ForEach)statement, this));
        else if (statement instanceof xowl.lang.actions.If) stackStatements.peek().push(new StatementIfRunner((xowl.lang.actions.If)statement, this));
        else if (statement instanceof xowl.lang.actions.Invoke) stackStatements.peek().push(new StatementInvokeRunner((xowl.lang.actions.Invoke)statement, this));
        else if (statement instanceof xowl.lang.actions.Execute) stackStatements.peek().push(new StatementExecuteRunner((xowl.lang.actions.Execute)statement, this));
        else if (statement instanceof xowl.lang.actions.Return) stackStatements.peek().push(new StatementReturnRunner((xowl.lang.actions.Return)statement, this));
        else if (statement instanceof xowl.lang.actions.Declare) stackStatements.peek().push(new StatementDeclareRunner((xowl.lang.actions.Declare)statement, this));
        else if (statement instanceof xowl.lang.actions.While) stackStatements.peek().push(new StatementWhileRunner((xowl.lang.actions.While)statement, this));
        else if (statement instanceof xowl.lang.interop.JavaInvokation) stackStatements.peek().push(new StatementJavaInvokationRunner((xowl.lang.interop.JavaInvokation)statement, this));
    }
    public void statementPop() { stackStatements.peek().pop(); }
    public void statementReturn(xowl.lang.runtime.Value value) {
        stackStatements.peek().clear();
        stackContexts.peek().clear();
        returnValue = value;
    }
    public void statementBreak() {
        while (stackStatements.peek().pop().onBreak()) { }
    }
    public void statementAdd(xowl.lang.owl2.Axiom axiom) throws XOWLException { repository.apply(evaluator, new xowl.interpreter.Change(runtimeOntology, axiom, true)); }
    public void statementRemove(xowl.lang.owl2.Axiom axiom) throws XOWLException { repository.apply(evaluator, new xowl.interpreter.Change(runtimeOntology, axiom, false)); }
    
    public ThreadContext contextCreate() {
        ThreadContext context = new ThreadContext(stackContexts.peek().peek());
        stackContexts.peek().push(context);
        return context;
    }
    public void contextPop() {
        stackContexts.peek().pop();
    }
    public xowl.lang.runtime.LexicalContext contextGetBounds() {
        if (stackContexts.isEmpty())
            return new xowl.lang.runtime.LexicalContext();
        if (stackContexts.peek().isEmpty())
            return new xowl.lang.runtime.LexicalContext();
        return stackContexts.peek().peek().getBounds();
    }


    protected void fill(XOWLException error) throws XOWLException {
        if (error.getStack().isEmpty()) {
            error.setThread(this);
            error.setFile(stackStatements.peek().peek().getFile());
            error.setLine(stackStatements.peek().peek().getLine());
            
            List<String> names = new ArrayList<String>();
            for (xowl.lang.runtime.Closure elem : stackClosures) {
                xowl.lang.runtime.Function function = evaluator.toFunction(elem);
                if (function != null)
                    names.add(function.getInterpretationOf().getHasIRI().getHasValue());
                else
                    names.add("Anonymous Lambda");
            }
            List<String> positions = new ArrayList<String>();
            for (Stack<StatementRunner> inner : stackStatements) {
                StatementRunner top = inner.peek();
                String file = top.getFile();
                int line = top.getLine();
                positions.add("(" + file + ":" + line + ")");
            }
            for (int i=names.size()-1; i!=-1; i--) {
                error.addStackElement(names.get(i) + " " + positions.get(i));
            }
        }
    }
}
